package com.lulin.mall.seckill.service.impl;

import com.alibaba.fastjson.JSON;
import com.lulin.common.constant.SeckillConstant;
import com.lulin.common.utils.R;
import com.lulin.mall.seckill.dto.SeckillSkuRedisDto;
import com.lulin.mall.seckill.fegin.CouponFeginService;
import com.lulin.mall.seckill.fegin.ProductFeginService;
import com.lulin.mall.seckill.service.SeckillService;
import com.lulin.mall.seckill.vo.SeckillSessionEntityVO;
import com.lulin.mall.seckill.vo.SkuInfoEntityVO;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * @author lulin
 * @date 2023/1/4 11:21
 */
@Service
public class SeckillServiceImpl implements SeckillService {
    @Autowired
    CouponFeginService couponFeginService;

    @Autowired
    StringRedisTemplate redisTemplate;

    @Autowired
    ProductFeginService productFeginService;

    @Autowired
    RedissonClient redissonClient;

    /**
     * 根据skuId，查询秒杀活动对应的商品信息
     *
     * @param skuId sku商品编号
     */
    @Override
    public SeckillSkuRedisDto getSeckillSessionBySkuId(Long skuId) {
        //找到所有需要参与秒杀的商品sku信息
        BoundHashOperations<String, String, String> ops = redisTemplate.boundHashOps(SeckillConstant.SKU_CHACE_PREFIX);
        Set<String> keys = ops.keys();
        if (keys != null && keys.size() > 0) {
            //正则表达式
            String regx = "\\d_" + skuId;
            for (String key : keys) {
                boolean matches = Pattern.matches(regx, key);
                if (matches) {
                    //说明找到了对应的sku信息
                    String json = ops.get(key);
                    SeckillSkuRedisDto dto = JSON.parseObject(json, SeckillSkuRedisDto.class);
                    return dto;
                }
            }
        }
        return null;
    }

    /**
     * 调用上架商品的方法
     */
    @Override
    public void uploadSeckillSku3Days() {
        //1、通过openFegin，远程调用coupon服务中的接口，来获取未来3天秒杀活动的商品信息
        R r = couponFeginService.getLate3DaysSeckillSkuInfo();
        if (r.getCode() == 0) {
            //表示查询操作成功
            // List<SeckillSessionEntityVO> seckillSessionEntityVOSList = (List<SeckillSessionEntityVO>) r.get("data");
            String json = (String) r.get("data");
            List<SeckillSessionEntityVO> seckillSessionEntityVOSList = JSON.parseArray(json, SeckillSessionEntityVO.class);

            //2、上架商品（放redis中）
            //2.1、缓存每日秒杀活动的sku信息
            saveSessionInfosToRedis(seckillSessionEntityVOSList);
            //2.2、缓存每日秒杀活动对应的sku的详细商品信息s
            saveSessionSkuInfosToRedis(seckillSessionEntityVOSList);

        }
    }

    /**
     * 当前时间+1天
     */
    private Long startTime() {
        Calendar calendar = Calendar.getInstance();
        //加1天
        calendar.add(Calendar.DATE, +1);
        Long time = calendar.getTime().getTime();
        return time;
    }

    /**
     * 查询当前时间内人秒杀活动以及对应的商品sku信息
     */
    @Override
    public List<SeckillSkuRedisDto> getCurrentSeckillSkus() {
        // 1.确定当前时间是属于哪个秒杀活动的
        // long time = new Date().getTime();
        // long time = System.currentTimeMillis();
        long time = startTime();//创建数据时，是未来3天的数据，这里未来测试，多加1天
        //从redis中查询索引的描述活动
        Set<String> keys = redisTemplate.keys(SeckillConstant.SSESSION_CHACE_PREFIX + "*");

        //循环找
        for (String key : keys) {
            //存在redis中的格式：seckill:session:1672974000000_1672889400000
            String replace = key.replace(SeckillConstant.SSESSION_CHACE_PREFIX, "");
            //把seckill:session:替换为空后，得到1672974000000_1672889400000 根据下划线截取转换
            String[] split = replace.split("_");
            //拿到活动开始和结束的时间
            Long start = Long.parseLong(split[0]);
            Long end = Long.parseLong(split[1]);
            //进行判断，如果当前时间在开始时间和结束时间之间，说明秒杀活动，就是当前时间需要参与的活动
            if (time > start && time < end) {
                //在session里面，获取到sku的id  格式：2_3 (活动场次_skuId)
                List<String> range = redisTemplate.opsForList().range(key, -100, 100);//key在-100到100之间的
                BoundHashOperations<String, String, String> hashOps = redisTemplate.boundHashOps(SeckillConstant.SKU_CHACE_PREFIX);
                //根据key，一次性全部拿出来   skus商品信息
                List<String> list = hashOps.multiGet(range);//有多个

                //如果有商品
                if (list != null && list.size() > 0) {
                    //返回秒杀活动的sku商品信息
                    List<SeckillSkuRedisDto> collect = list.stream().map(item -> {
                        //它是json数据，要转换
                        SeckillSkuRedisDto seckillSkuRedisDto = JSON.parseObject(item, SeckillSkuRedisDto.class);
                        return seckillSkuRedisDto;
                    }).collect(Collectors.toList());
                    return collect;
                }
            }
        }
        return null;
    }


    /**
     * 保存每日秒杀活动信息到redis中
     */
    private void saveSessionInfosToRedis(List<SeckillSessionEntityVO> seckillSessionEntityVOSList) {
        //循环缓存每一个活动 key：start_end
        for (SeckillSessionEntityVO seckillSessionEntityVO : seckillSessionEntityVOSList) {
            //获取时间
            long start = seckillSessionEntityVO.getStartTime().getTime();
            long end = seckillSessionEntityVO.getEndTime().getTime();
            //生成key
            String key = SeckillConstant.SSESSION_CHACE_PREFIX + start + "_" + end;

            //保证幂等性——如果上架过了，就不上了
            Boolean flag = redisTemplate.hasKey(key);
            //表示这个秒杀活动在redis中不存在，也就是还没有上架，则需要保存
            if (!flag) {
                //存储到redis中的这个秒杀活动涉及刀到的商品信息的sku_id
                List<String> list = seckillSessionEntityVO.getRelationEntities().stream().map(item -> {
                    // 秒杀活动存储的 VALUE是 sessionId_SkuId
                    return item.getPromotionSessionId() + "_" + item.getSkuId().toString();
                }).collect(Collectors.toList());
                //保存到redis中
                redisTemplate.opsForList().leftPushAll(key, list);
            }
        }
    }

    /**
     * 保存活动对应的 sku的详细信息
     */
    private void saveSessionSkuInfosToRedis(List<SeckillSessionEntityVO> seckillSessionEntityVOSList) {
        //循环取出每个session活动场次，然后，取出对应的sku信息，并封装
        seckillSessionEntityVOSList.stream().forEach(session -> {
            BoundHashOperations<String, Object, Object> hashOps = redisTemplate.boundHashOps(SeckillConstant.SKU_CHACE_PREFIX);

            session.getRelationEntities().stream().forEach(item -> {
                //sku的key
                String skuKey = item.getPromotionSessionId() + "_" + item.getSkuId();
                //保证幂等性
                Boolean flag = redisTemplate.hasKey(skuKey);
                // 表示 该秒杀活动对应的sku的信息在redis中不存在，即没有上架，需要保存
                if (!flag) {
                    SeckillSkuRedisDto dto = new SeckillSkuRedisDto();
                    //1、获取sku的基本信息
                    R info = productFeginService.info(item.getSkuId());
                    if (info.getCode() == 0) {
                        //表示查询成功
                        //SkuInfoEntityVO skuInfo = (SkuInfoEntityVO) info.get("skuInfo");
                        //dto.setSkuInfoEntityVO(skuInfo);
                        String json = (String) info.get("skuInfoJSON");
                        dto.setSkuInfoEntityVO(JSON.parseObject(json, SkuInfoEntityVO.class));
                    }
                    //2、获取sku的秒杀信息
                    BeanUtils.copyProperties(item, dto);//item复制到dto

                    //3、设置当前商品的秒杀时间
                    dto.setStartTime(session.getStartTime().getTime());
                    dto.setStartTime(session.getEndTime().getTime());

                    //随机码
                    String token = UUID.randomUUID().toString().replace("-", "");
                    dto.setRandCode(token);

                    //分布式信息量处理——限流
                    RSemaphore semaphore = redissonClient.getSemaphore(SeckillConstant.SKU_STOCK_SEMAPHORE + token);
                    // 把秒杀活动的商品数量，作为分布式信号量的值（信号量）
                    semaphore.trySetPermits(item.getSeckillCount().intValue());//秒杀总量

                    hashOps.put(skuKey, JSON.toJSONString(dto));
                }
            });
        });
    }

}

